<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>Advanced Controls in Mono Winforms</title>
<link rel="stylesheet" href="/cfg/format.css" type="text/css">
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<meta name="keywords" content="Winforms, Mono, IronPython, advanced controls, programming, tutorial">
<meta nam="description" content="Advanced Controls">
<meta name="language" content="en">
<meta name="author" content="Jan Bodnar">
<meta name="distribution" content="global">

<script type="text/javascript" src="/lib/jquery.js"></script>
<script type="text/javascript" src="/lib/common.js"></script>

</head>

<body>

<div class="container">

<div id="wide_ad" class="ltow">
<script type="text/javascript"><!--
google_ad_client = "pub-9706709751191532";
/* 160x600, August 2011 */
google_ad_slot = "2484182563";
google_ad_width = 160;
google_ad_height = 600;
//-->
</script>
<script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js">
</script>
</div>

<div class="content">


<a href="/" title="Home">Home</a>&nbsp;
<a href=".." title="Home">Contents</a>


<h1>Advanced Controls in Mono Winforms</h1>


<p>
In this part of the IronPython Mono Winforms tutorial, we introduce some 
more advanced controls. Namely the <b class="keyword">ListBox</b>, the 
<b class="keyword">ListView</b> and the <b class="keyword">TreeView</b> control.
</p>

<div class="center">
<script type="text/javascript"><!--
google_ad_client = "pub-9706709751191532";
/* horizontal */
google_ad_slot = "1734478269";
google_ad_width = 468;
google_ad_height = 60;
//-->
</script>
<script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js">
</script>
</div>

<h2>ListBox Control</h2>

<p>
The <b class="keyword">ListBox</b> control is used to display a list 
of items. Users can select one or more items by clicking on them. 
</p>

<div class="codehead">listbox.py</div>
<pre class="code">
#!/usr/bin/ipy

import clr
clr.AddReference("System.Windows.Forms")
clr.AddReference("System.Drawing")

from System.Windows.Forms import Application, Form, StatusBar
from System.Windows.Forms import ListBox, DockStyle
from System.Drawing import Size


class IForm(Form):

    def __init__(self):
        self.Text = "ListBox"
        
        lb = ListBox()
        lb.Parent = self

        lb.Items.Add("Jessica")
        lb.Items.Add("Rachel")
        lb.Items.Add("Angelina")
        lb.Items.Add("Amy")
        lb.Items.Add("Jennifer")
        lb.Items.Add("Scarlett")

        lb.Dock = DockStyle.Fill
        lb.SelectedIndexChanged += self.OnChanged

        self.sb = StatusBar()
        self.sb.Parent = self

        self.Size = Size(220, 220)
        self.CenterToScreen()
    

    def OnChanged(self, sender, event):
        self.sb.Text = sender.SelectedItem


Application.Run(IForm())
</pre>

<p>
Our example shows a listbox with six names. The selected item is shown in the statusbar. 
</p>

<pre class="explanation">
 lb = ListBox()
 lb.Parent = self
</pre>

<p>
<b class="keyword">ListBox</b> control is created. 
</p>

<pre class="explanation">
 lb.Items.Add("Jessica")
</pre>

<p>
This is how we add a new item to the <b class="keyword">ListBox</b> control. 
The control has the <b class="keyword">Items</b> property. The property is 
a reference to the list of items in a listbox. Using this reference, we can 
add, remove or get count of items of the listbox. 
</p>

<pre class="explanation">
 lb.SelectedIndexChanged += self.OnChanged
</pre>

<p>
<b class="keyword">SelectedIndexChanged</b> event is triggered, when we select an item. 
</p>

<pre class="explanation">
 def OnChanged(self, sender, event):
     self.sb.Text = sender.SelectedItem
</pre>

<p>
Inside the <b class="keyword">OnChange()</b> method, we get selected 
value of the listbox and set it to the statusbar.
</p>


<br>
<img src="/img/gui/ironpython/listbox.png" alt="ListBox">
<div class="figure">Figure: ListBox</div>


<h2>ListView</h2>

<p>
<b class="keyword">ListView</b> control is used to display collections of items.
It is a more sophisticated control than the <b class="keyword">ListBox</b> control.
It can display data in various views. It is mostly used to display data in multicolumn views. 
</p>

<div class="codehead">listview.py</div>
<pre class="code">
#!/usr/bin/ipy

import clr
clr.AddReference("System.Windows.Forms")
clr.AddReference("System.Drawing")

from System.Windows.Forms import Application, Form, StatusBar
from System.Windows.Forms import ListView, View, ColumnHeader
from System.Windows.Forms import ListViewItem, DockStyle, SortOrder
from System.Drawing import Size


class IForm(Form):

    def __init__(self):
        self.Text = 'ListBox'
       

        actresses = { 'Jessica Alba' : '1981', 'Angelina Jolie' : '1975', 
            'Natalie Portman' : '1981', 'Rachel Weiss' : '1971', 
            'Scarlett Johansson' : 1984 }


        name = ColumnHeader()
        name.Text = 'Name'
        name.Width = -1
        year = ColumnHeader()
        year.Text = 'Year'

        self.SuspendLayout()

        lv = ListView()
        lv.Parent = self
        lv.FullRowSelect = True
        lv.GridLines = True
        lv.AllowColumnReorder = True
        lv.Sorting = SortOrder.Ascending
        lv.Columns.AddRange((name, year))
        lv.ColumnClick += self.OnColumnClick

        for act in actresses.keys():
            item = ListViewItem()
            item.Text = act
            item.SubItems.Add(str(actresses[act]))
            lv.Items.Add(item)
        

        lv.Dock = DockStyle.Fill
        lv.Click += self.OnChanged

        self.sb = StatusBar()
        self.sb.Parent = self
        lv.View = View.Details

        self.ResumeLayout()

        self.Size = Size(350, 300)
        self.CenterToScreen()
    

    def OnChanged(self, sender, event):

        name = sender.SelectedItems[0].SubItems[0].Text
        born = sender.SelectedItems[0].SubItems[1].Text
        self.sb.Text = name + ', ' + born
    

    def OnColumnClick(self, sender, event):

        if sender.Sorting == SortOrder.Ascending:
            sender.Sorting = SortOrder.Descending
        else: 
            sender.Sorting = SortOrder.Ascending


Application.Run(IForm())
</pre>

<p>
In our example, we have a listview with two columns. In the first column, 
we display the name of the actress. In the second one their date of birth. 
The data is store in a <b class="keyword">List</b> collection. By selecting 
a row, the data in a row is displayed in the statusbar. Also, by clicking 
on the column header, the data is sorted. 
</p>

<pre class="explanation">
 actresses = { 'Jessica Alba' : '1981', 'Angelina Jolie' : '1975', 
     'Natalie Portman' : '1981', 'Rachel Weiss' : '1971', 
     'Scarlett Johansson' : 1984 }
</pre>

<p>
We store our data in the actresses dictionary.
</p>

<pre class="explanation">
 name = ColumnHeader()
 name.Text = 'Name'
 name.Width = -1
</pre>

<p>
For each column in a listview, we create a <b class="keyword">ColumnHeader</b>. 
By setting the  <b class="keyword">Width</b> to -1, the width of the column is 
equal to the longest item in the column. 
</p>

<pre class="explanation">
 lv = ListView()
 lv.Parent = self
</pre>

<p>
<b class="keyword">ListView</b> control is created. 
</p>

<pre class="explanation">
 lv.FullRowSelect = True
 lv.GridLines = True
 lv.AllowColumnReorder = True
 lv.Sorting = SortOrder.Ascending
</pre>

<p>
Here we set four properties of the control. This code lines enable full 
row selection, show grid lines, allow column reordering by dragging the 
columns and sort the data in ascending order.
</p>

<pre class="explanation">
 lv.Columns.AddRange((name, year))
</pre>

<p>
Here we add two <b class="keyword">ColumnHeader</b>s to the 
<b class="keyword">ListView</b> control. 
</p>

<pre class="explanation">
 for act in actresses.keys():
     item = ListViewItem()
     item.Text = act
     item.SubItems.Add(str(actresses[act]))
     lv.Items.Add(item)
</pre>

<p>
This cycle populates the listview control. Each row is added to the listview 
as a <b class="keyword">ListViewItem</b> class.
</p>


<pre class="explanation">
 lv.View = View.Details
</pre>

<p>
The <b class="keyword">ListView</b> control can have different views. 
Different views display data differently.
</p>

<pre class="explanation">
 name = sender.SelectedItems[0].SubItems[0].Text
 born = sender.SelectedItems[0].SubItems[1].Text
 self.sb.Text = name + ', ' + born
</pre>

<p>
Inside the <b class="keyword">OnChanged()</b> method, we get the data 
from the selected row and show it on the statusbar. 
</p>


<pre class="explanation">
 if sender.Sorting == SortOrder.Ascending:
     sender.Sorting = SortOrder.Descending
 else: 
     sender.Sorting = SortOrder.Ascending
</pre>

<p>
Here we toggle the sorting order of the column. 
</p>

<br>
<img src="/img/gui/ironpython/listview.png" alt="ListView">
<div class="figure">Figure: ListView</div>


<h2>TreeView</h2>

<p>
<b class="keyword">TreeView</b> control displays hierarchical collection 
of items. Each item in this control is represented by a <b class="keyword">TreeNode</b> object. 
</p>

<div class="codehead">treeview.py</div>
<pre class="code">
#!/usr/bin/ipy

import clr
clr.AddReference("System.Windows.Forms")
clr.AddReference("System.Drawing")

from System.Windows.Forms import Application, Form, StatusBar
from System.Windows.Forms import TreeView, TreeNode, DockStyle
from System.Drawing import Size


class IForm(Form):

    def __init__(self):
        self.Text = 'TreeView'
        
        tv = TreeView()

        root = TreeNode()
        root.Text = 'Languages'

        child1 = TreeNode()
        child1.Text = 'Python'

        child2 = TreeNode()
        child2.Text = 'Ruby'

        child3 = TreeNode()
        child3.Text = 'Java'

        root.Nodes.AddRange((child1, child2, child3))

        tv.Parent = self
        tv.Nodes.Add(root)
        tv.Dock = DockStyle.Fill
        tv.AfterSelect += self.AfterSelect

        self.sb = StatusBar()
        self.sb.Parent = self

        self.Size = Size(220, 220)
        self.CenterToScreen()
    

    def AfterSelect(self, sender, event):    
        self.sb.Text = event.Node.Text
    

Application.Run(IForm())
</pre>

<p>
This is a very simple demonstration of the <b class="keyword">TreeView</b> control.
We have one root item and three children. 
</p>


<pre class="explanation">
 tv = TreeView()
</pre>

<p>
We create the <b class="keyword">TreeView</b> control.
</p>

<pre class="explanation">
 root = TreeNode()
 root.Text = 'Languages'
 ...
 tv.Nodes.Add(root)
</pre>

<p>
Here we create a root node. 
</p>

<pre class="explanation">
 child1 = TreeNode()
 child1.Text = 'Python'
</pre>

<p>
Child node is created in a similar way. 
</p>

<pre class="explanation">
 root.Nodes.AddRange((child1, child2, child3))
</pre>

<p>
Child nodes are plugged into the <b class="keyword">Nodes</b> property of the root node. 
</p>


<br>
<img src="/img/gui/ironpython/treeview.png" alt="TreeView">
<div class="figure">Figure: TreeView</div>


<h2>Directories</h2>

<p>
The following code example will examine the <b class="keyword">TreeView</b> control more
in-depth. 
</p>

<div class="codehead">directories.py</div>
<pre class="code">
#!/usr/bin/ipy

import clr
clr.AddReference("System.Windows.Forms")
clr.AddReference("System.Drawing")

from System.Windows.Forms import Application, Form, StatusBar
from System.Windows.Forms import Button, TreeView, TreeNode
from System.Windows.Forms import DockStyle, AnchorStyles
from System.Drawing import Size, Point
from System.IO import Directory, DirectoryInfo


HOME_DIR = '/home/vronskij'

class IForm(Form):

    def __init__(self):
        self.Text = 'Directories'
        self.Size = Size(400, 400)
        
        self.tv = TreeView()

        self.SuspendLayout()

        self.tv.Parent = self
        self.tv.Location = Point(10,10)
        self.tv.Size = Size(self.ClientSize.Width - 20, self.Height - 200)
        self.tv.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right 


        self.tv.FullRowSelect = False
        self.tv.ShowLines = True
        self.tv.ShowPlusMinus = True
        self.tv.Scrollable = True  
        self.tv.AfterSelect += self.AfterSelect

        expand = Button()
        expand.Parent = self
        expand.Location = Point(20, self.tv.Bottom + 20)
        expand.Text = 'Expand'
        expand.Anchor = AnchorStyles.Left | AnchorStyles.Top
        expand.Click += self.OnExpand

        expandAll = Button()
        expandAll.Parent = self
        expandAll.Location = Point(20, expand.Bottom + 5)
        expandAll.Text = 'Expand All'
        expandAll.Anchor = AnchorStyles.Left | AnchorStyles.Top
        expandAll.Click += self.OnExpandAll

        collapse = Button()
        collapse.Parent = self
        collapse.Location = Point(expandAll.Right + 5, expand.Top)
        collapse.Text = 'Collapse'
        collapse.Anchor = AnchorStyles.Left | AnchorStyles.Top
        collapse.Click += self.OnCollapse

        collapseAll = Button()
        collapseAll.Parent = self
        collapseAll.Location = Point(collapse.Left, collapse.Bottom + 5)
        collapseAll.Text = 'Collapse All'
        collapseAll.Anchor = AnchorStyles.Left | AnchorStyles.Top
        collapseAll.Click += self.OnCollapseAll

        self.sb = StatusBar()
        self.sb.Parent = self

        self.ShowDirectories(self.tv.Nodes, HOME_DIR)

        self.ResumeLayout()

        self.CenterToScreen()
    
    def AfterSelect(self, sender, event):
  
        self.sb.Text = event.Node.Text
        
    def ShowDirectories(self, trvNode, path):
    
        dirInfo = DirectoryInfo(path)
        if (dirInfo != None):
      
          subDirs = dirInfo.GetDirectories()
          tr = TreeNode(dirInfo.Name)

          if (subDirs.Length > 0):
          
              for dr in subDirs: 
                  if not dr.Name.StartsWith("."):
                      self.ShowDirectories(tr.Nodes, dr.FullName)
                        
          trvNode.Add(tr)    

      
    def OnExpand(self, sender, event):
        self.tv.SelectedNode.Expand()
  

    def OnExpandAll(self, sender, event):
        self.tv.ExpandAll()
  

    def OnCollapse(self, sender, event):
        self.tv.SelectedNode.Collapse()
  

    def OnCollapseAll(self, sender, event):
        self.tv.CollapseAll()


Application.Run(IForm())
</pre>

<p>
Our code example shows the directories of the specified home directory in 
a <b class="keyword">TreeView</b> control. The application starts with some delay,
because it reads the directory structure of the home directory first. 
We have also four buttons on the form. The buttons expand and collapse nodes
programatically. 
</p>


<pre class="explanation">
 self.tv.Scrollable = True 
</pre>

<p>
We make the treeview control scrollable, because the control shows lots of 
directories. 
</p>


<pre class="explanation">
 self.ShowDirectories(self.tv.Nodes, HOME_DIR)
</pre>

<p>
The <b class="keyword">ShowDirectories()</b> method fills the nodes of the
treview control with directories available in the specified home directory. 
</p>

<pre class="explanation">
 if (subDirs.Length > 0):
     ...
</pre>

<p>
We check if there are any subdirectories. 
</p>

<pre class="explanation">
 for dr in subDirs: 
     if not dr.Name.StartsWith("."):
         self.ShowDirectories(tr.Nodes, dr.FullName)
</pre>

<p>
We loop through all directories. For this, we use the recursion algorithm.
We also skip the hidden directories. They begin with a dot on Unix systems. 
</p>

<pre class="explanation">
 trvNode.Add(tr)     
</pre>

<p>
This code line actually adds the directory to the treeview control. 
</p>

<pre class="explanation">
 def OnExpand(self, sender, event):
     self.tv.SelectedNode.Expand()
</pre>

<p>
All four buttons have events plugged to a method. Here is a method for the Expand button.
It calls the <b class="keyword">Expand()</b> method of the currently selected node.
</p>

<br>
<img src="/img/gui/ironpython/directories.png" alt="Directories">
<div class="figure">Figure: Directories</div>

<hr class="btm">

<p>
In this part of the IronPython Mono Winforms tutorial, we covered 
several advanced controls available in Winforms programming library.
</p>

<div class="center">
<script type="text/javascript"><!--
google_ad_client = "pub-9706709751191532";
/* horizontal */
google_ad_slot = "1734478269";
google_ad_width = 468;
google_ad_height = 60;
//-->
</script>
<script type="text/javascript"
src="http://pagead2.googlesyndication.com/pagead/show_ads.js">
</script>
</div>
<br>

<div class="botNav, center">
<span class="botNavItem"><a href="/">Home</a></span> ‡ <span class="botNavItem"><a href="..">Contents</a></span> ‡ 
<span class="botNavItem"><a href="#">Top of Page</a></span>
</div>


<div class="footer">
<div class="signature">
<a href="/">ZetCode</a> last modified January 31, 2009  <span class="copyright">&copy; 2007 - 2012 Jan Bodnar</span>
</div>
</div>

</div> <!-- content -->

</div> <!-- container -->

</body>
</html>

